// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: taekwon

#pragma once

#include <string>

#include "coin2/base/time.h"
#include "coin2/exchange/base/api_base/asio_http_context.h"
#include "coin2/exchange/base/api_base/base_auth.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/base/api_util/json_writer.h"
#include "coin2/exchange/base/api_util/url_params_builder.h"

class HuobiBaseAuth {
 public:
#define LIKELY(x) __builtin_expect(!!(x), 1)

  virtual ~HuobiBaseAuth() = default;

  static std::string GetCurrIso8601UntilSecond() {
    return Iso8601FromTimestamp(GetCurrentTimestamp()).substr(0, 19);
  }

  static std::string GetSignature(
      const std::string& host,
      const std::string& access_key,
      const std::string& secret_key,
      const std::string& address,
      const std::string& method,
      const std::string& time,
      const std::string& param) {
    if (access_key.empty() || secret_key.empty()) {
      throw std::runtime_error("[HuobiFutures] API key and secret key are required");
    }
    CHECK(LIKELY(
        host == "xiguaapi.hbdm.com" || host == "api.hbdm.com" || host == "api.hbdm.vn" ||
        host == "api.huobi.pro" || host == "661562598406.coloc.huobi.com" ||
        host == "api-aws.huobi.pro"));
    static std::string host_override;
    if (host == "xiguaapi.hbdm.com") {
      host_override = "api.hbdm.com";
    } else {
      host_override = host;
    }
    std::string cre;
    if (address == "/ws/v2") {
      cre = method + "\n" + host_override + "\n" + address + "\n" + "accessKey=" + access_key +
            "&signatureMethod=HmacSHA256" + "&signatureVersion=2.1" + "&timestamp=" + time;
    } else {
      cre = method + "\n" + host_override + "\n" + address + "\n" + "AccessKeyId=" + access_key +
            "&SignatureMethod=HmacSHA256" + "&SignatureVersion=2" + "&Timestamp=" + time;
    }
    if (!param.empty()) {
      cre = cre + "&" + param;
    }
    return ApiSignatureUtil::GetSignatureByHmacSHA256(cre, secret_key);
  }
};

class HuobiWsAuth : public HuobiBaseAuth {
 public:
  explicit HuobiWsAuth(const std::string& key_filepath) : key_(AuthKey::FromFile(key_filepath)) {}

  const std::string CreateSignature(const std::string& host, const std::string& address) const {
    std::string signa = GetSignature(
        host,
        key_.access_key(),
        key_.secret_key(),
        address,
        "GET",
        ApiSignatureUtil::EscapeURL(GetCurrIso8601UntilSecond()),
        "");

    JsonWriter writer;
    writer.PutString("op", "auth");
    writer.PutString("type", "api");
    writer.PutString("AccessKeyId", key_.access_key());
    writer.PutString("SignatureVersion", "2");
    writer.PutString("Signature", signa);
    writer.PutString("SignatureMethod", "HmacSHA256");
    writer.PutString("Timestamp", GetCurrIso8601UntilSecond());
    return writer.ToJsonString();
  }

  const std::string CreateSignatureWs2(const std::string& host, const std::string& address) const {
    std::string ts_str = GetCurrIso8601UntilSecond();
    std::string signa = GetSignature(
        host,
        key_.access_key(),
        key_.secret_key(),
        address,
        "GET",
        ApiSignatureUtil::EscapeURL(ts_str),
        "");

    JsonWriter params;
    params.PutString("authType", "api");
    params.PutString("accessKey", key_.access_key());
    params.PutString("signatureVersion", "2.1");
    params.PutString("signature", signa);
    params.PutString("signatureMethod", "HmacSHA256");
    params.PutString("timestamp", ts_str);

    return fmt::format(R"({{"action": "req", "ch": "auth", "params": {}}})", params.ToJsonString());
  }

 protected:
  AuthKey key_;
};

class HuobiRestAuth : public HuobiBaseAuth, public BaseRestAuth {
 public:
  explicit HuobiRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      const std::string& port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(port), bind_ip_(bind_ip) {}
  explicit HuobiRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      int32_t port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)),
        host_(host),
        port_(std::to_string(port)),
        bind_ip_(bind_ip) {}

  AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    CHECK(post_body.empty());
    std::string signa = BuildSignaturePath(
        host_,
        key_.access_key(),
        key_.secret_key(),
        address,
        "GET",
        ApiSignatureUtil::EscapeURL(GetCurrIso8601UntilSecond()),
        query_string);

    std::string target;
    if (!query_string.empty()) {
      target = address + "?" + query_string + "&" + signa;
    } else {
      target = address + "?" + signa;
    }

    return AsioHttpContext::NewGet(host_, port_, bind_ip_, target);
  }

  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& address,
      const std::string& query_string,
      const std::string& post_body) {
    std::string signa = BuildSignaturePath(
        host_,
        key_.access_key(),
        key_.secret_key(),
        address,
        "POST",
        ApiSignatureUtil::EscapeURL(GetCurrIso8601UntilSecond()),
        query_string);

    std::string target;
    if (!query_string.empty()) {
      target = address + "?" + query_string + "&" + signa;
    } else {
      target = address + "?" + signa;
    }

    return AsioHttpContext::NewPost(host_, port_, bind_ip_, target, post_body);
  }

  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& address,
      const std::string& body) {
    return CreateRequestByPostWithSignature(address, "", body);
  }

  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();

    return CreateRequestByPostWithSignature(address, query_string, post_body);
  }

  AsioHttpContext* CreateRequestByDeleteWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    return nullptr;
  }

  AsioHttpContext* CreateRequestByPutWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    return nullptr;
  }

 private:
  static std::string BuildSignaturePath(
      const std::string& host,
      const std::string& access_key,
      const std::string& secret_key,
      const std::string& address,
      const std::string& method,
      const std::string& time,
      const std::string& param) {
    std::string code = ApiSignatureUtil::EscapeURL(
        GetSignature(host, access_key, secret_key, address, method, time, param));
    std::string res = "AccessKeyId=" + access_key + "&SignatureMethod=HmacSHA256" +
                      "&SignatureVersion=2" + "&Timestamp=" + time + "&Signature=" + code;

    return res;
  }

 protected:
  AuthKey key_;
  std::string host_;
  std::string port_;
  std::string bind_ip_;
};
